﻿using System.Linq;
using Borg.AddOn;
using Borg.Properties;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;

namespace Borg
{
    [Serializable]
    public class Composer : RuntimeElement, ICreator, IComposer
    {
        protected bool allowComposition;

        private InstanceBuilder _instanceBuilder;

        private HashSet<Extension> _isCreatingSet;
        private HashSet<Extension> _isActivatingSet;
        private HashSet<Extension> _isDestroyingSet;

        private HashSet<Slot> _isOpeningSet;
        private HashSet<Slot> _isClosingSet;

        private Dictionary<Slot, List<Plug>> _isPluggingDictionary;
        private Dictionary<Slot, List<Tag>> _isTaggingDictionary;

        private Dictionary<Slot, List<Plug>> _isUnpluggingDictionary;
        private Dictionary<Slot, List<Tag>> _isUntaggingDictionary;
        
        private Dictionary<Extension, object> _isDeactivatingDictionary;
        
        private IComposerApiHook _apiHook;
        private IComposerSubstitutionHook _substitutionHook;
        private ICompositionPermissionHook _compositionPermissionHook;
        private IComposerNotificationHook _notificationHook;

        private bool _isBusy;

        public bool AllowComposition
        {
            get { return allowComposition; }
        }

        public BehaviorCollection Behaviors
        {
            get { return Notifier.GetComposerBehaviors(); }
        }

        private HashSet<Extension> IsCreatingSet
        {
            get { return _isCreatingSet; }
        }

        private HashSet<Extension> IsActivatingSet
        {
            get { return _isActivatingSet; }
        }

        private HashSet<Slot> IsOpeningSet
        {
            get { return _isOpeningSet; }
        }

        private Dictionary<Slot, List<Plug>> IsPluggingDictionary
        {
            get { return _isPluggingDictionary; }
        }

        private Dictionary<Slot, List<Tag>> IsTaggingDictionary
        {
            get { return _isTaggingDictionary; }
        }

        private HashSet<Extension> IsDestroyingSet
        {
            get { return _isDestroyingSet; }
        }

        private Dictionary<Extension, object> IsDeactivatingDictionary
        {
            get { return _isDeactivatingDictionary; }
        }

        private HashSet<Slot> IsClosingSet
        {
            get { return _isClosingSet; }
        }

        private Dictionary<Slot, List<Plug>> IsUnpluggingDictionary
        {
            get { return _isUnpluggingDictionary; }
        }

        private Dictionary<Slot, List<Tag>> IsUntaggingDictionary
        {
            get { return _isUntaggingDictionary; }
        }

        public bool IsBusy
        {
            get { return _isBusy; }
            private set { _isBusy = value; }
        }

        public event EventHandler<EventArgs> CompositionDone
        {
            add { Notifier.AddCompositionDoneHandler(value); }
            remove { Notifier.RemoveCompositionDoneHandler(value); }
        }

        public event EventHandler<ExtensionEventArgs> ExtensionCreated
        {
            add { Notifier.AddExtensionCreatedHandler(value); }
            remove { Notifier.RemoveExtensionCreatedHandler(value); }
        }

        public event EventHandler<ExtensionEventArgs> ExtensionDestroyed
        {
            add { Notifier.AddExtensionDestroyedHandler(value); }
            remove { Notifier.RemoveExtensionDestroyedHandler(value); }
        }

        public event EventHandler<ExtensionEventArgs> ExtensionActivated
        {
            add { Notifier.AddExtensionActivatedHandler(value); }
            remove { Notifier.RemoveExtensionActivatedHandler(value); }
        }

        public event EventHandler<ExtensionEventArgs> ExtensionDeactivated
        {
            add { Notifier.AddExtensionDeactivatedHandler(value); }
            remove { Notifier.RemoveExtensionDeactivatedHandler(value); }
        }

        public event EventHandler<SlotEventArgs> SlotOpened
        {
            add { Notifier.AddSlotOpenedHandler(value); }
            remove { Notifier.RemoveSlotOpenedHandler(value); }
        }

        public event EventHandler<SlotEventArgs> SlotClosed
        {
            add { Notifier.AddSlotClosedHandler(value); }
            remove { Notifier.RemoveSlotClosedHandler(value); }
        }

        public event EventHandler<CompositionEventArgs> ExtensionPlugged
        {
            add { Notifier.AddExtensionPluggedHandler(value); }
            remove { Notifier.RemoveExtensionPluggedHandler(value); }
        }

        public event EventHandler<CompositionEventArgs> ExtensionUnplugged
        {
            add { Notifier.AddExtensionUnpluggedHandler(value); }
            remove { Notifier.RemoveExtensionUnpluggedHandler(value); }
        }

        public event EventHandler<TagEventArgs> ExtensionTagged
        {
            add { Notifier.AddExtensionTaggedHandler(value); }
            remove { Notifier.RemoveExtensionTaggedHandler(value); }
        }

        public event EventHandler<TagEventArgs> ExtensionUntagged
        {
            add { Notifier.AddExtensionUntaggedHandler(value); }
            remove { Notifier.RemoveExtensionUntaggedHandler(value); }
        }

        protected internal Composer(Runtime runtime)
            : base(runtime)
        { }

        internal void Init(IComposerApiHook apiHook, IComposerSubstitutionHook substitutionHook,
            ICompositionPermissionHook compositionPermissionHook, IComposerNotificationHook notificationHook)
        {
            _apiHook = apiHook;
            _substitutionHook = substitutionHook;
            _compositionPermissionHook = compositionPermissionHook;
            _notificationHook = notificationHook;
            Init();
        }

        protected virtual void Init()
        {
            allowComposition = true;

            _instanceBuilder = new InstanceBuilder();
            _isCreatingSet = new HashSet<Extension>();
            _isActivatingSet = new HashSet<Extension>();
            _isOpeningSet = new HashSet<Slot>();
            _isPluggingDictionary = new Dictionary<Slot, List<Plug>>();
            _isTaggingDictionary = new Dictionary<Slot, List<Tag>>();
            _isDestroyingSet = new HashSet<Extension>();
            _isDeactivatingDictionary = new Dictionary<Extension, object>();
            _isClosingSet = new HashSet<Slot>();
            _isUnpluggingDictionary = new Dictionary<Slot, List<Plug>>();
            _isUntaggingDictionary = new Dictionary<Slot, List<Tag>>();
        }

        protected internal virtual void OnContractAdded(Contract contract)
        { }

        protected internal virtual void OnPluginAdded(Plugin plugin)
        {
            foreach (var plugType in plugin.ExtensionTypes.
                SelectMany(extensionType => extensionType.PlugTypes))
                Engine.InternalAutoTagAndPlug(plugType);
        }

        protected internal virtual void OnContractRemoved(Contract contract)
        { }

        protected internal virtual void OnPluginRemoved(Plugin plugin)
        {
            InternalDestroyAllExtensions(plugin);
        }

        public Composer DisableComposition()
        {
            if (!AllowComposition)
                throw new InvalidOperationException(Messages.Composer_Error_CompositionIsAlreadyDisabled);
            
            _apiHook.OnSetProperty(ComposerOperation.DisableComposition);
            
            return InternalDisableComposition();
        }

        internal Composer InternalDisableComposition()
        {
            var composer = (Composer)MemberwiseClone();

            InternalSetAllowComposition(false);
            
            return composer;
        }

        public Composer EnableComposition()
        {
            if (!AllowComposition)
                throw new InvalidOperationException(
                    Messages.Composer_Error_CompositionCanNotBeEnabledWithThisComposerInstance);
            
            _apiHook.OnSetProperty(ComposerOperation.EnableComposition);
            
            return InternalEnableComposition();
        }

        internal Composer InternalEnableComposition()
        {
            InternalSetAllowComposition(false);
            Runtime.Composer.InternalSetAllowComposition(true);
            
            return Runtime.Composer;
        }

        public Extension Create(ExtensionType extensionType)
        {
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");

            if (!CheckAccess())
                throw GetAccessException();
            
            _apiHook.OnExtensionTypeOperation(ExtensionTypeOperation.Instantiate, extensionType);
            LogArgs log;
            
            if (!InternalCanCreate(extensionType, out log))
            {
                var msg =
                    log != null
                        ? log.Message
                        : string.Empty;

                throw new InvalidOperationException(
                    string.Format(
                        Messages.Composer_ErrorOnCreatingExtension,
                        extensionType.Id,
                        extensionType.Name,
                        msg));
            }
            
            var wasBusy = BeginComposition();
            var extension = InternalCreate(extensionType);
            
            EndComposition(wasBusy);
            
            return extension;
        }

        public Extension GetShared(ExtensionType extensionType)
        {
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");

            if (!CheckAccess())
                throw GetAccessException();
            
            var sharedExtension = InstanceStore.InternalGetSharedExtension(extensionType);
            
            if (sharedExtension != null)
            {
                if (InternalIsCreating(sharedExtension))
                    Notifier.Continue();
                
                return sharedExtension;
            }
            
            LogArgs log;

            if (!InternalCanCreate(extensionType, out log))
            {
                var msg =
                    log != null
                        ? log.Message
                        : string.Empty;
                    
                throw new InvalidOperationException(
                    string.Format(
                        Messages.Composer_ErrorOnCreatingExtension,
                        extensionType.Id,
                        extensionType.Name,
                        msg));
            }
            
            var wasBusy = BeginComposition();
            var shared = InternalCreateShared(extensionType);
            
            EndComposition(wasBusy);
            
            return shared;
        }

        public void Destroy(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            if (!CheckAccess())
                throw GetAccessException();

            if (InstanceStore.InternalIsDestroyed(extension))
            {
                if (!InternalIsDestroying(extension))
                    return;

                Notifier.Continue();
            }
            else
            {
                LogArgs log;

                if (!InternalCanDestroy(extension, out log))
                {
                    var msg =
                        log != null
                            ? log.Message
                            : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnDestroyingExtension,
                            extension.Id,
                            extension.Name,
                            msg));
                }
                
                var wasBusy = BeginComposition();
                InternalDestroy(extension);
                EndComposition(wasBusy);
            }
        }

        public object GetExtensionObject(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            if (!CheckAccess())
                throw GetAccessException();
            
            _apiHook.OnExtensionOperation(ExtensionOperation.AccessObject, extension);
            
            if (InstanceStore.InternalIsActivated(extension))
            {
                if (InternalIsActivating(extension))
                    Notifier.Continue();
                
                return InstanceStore.InternalGetExtensionObject(extension);
            }

            if (InternalIsDeactivating(extension))
                return IsDeactivatingDictionary[extension];
                
            LogArgs log;

            if (!InternalCanActivate(extension, out log))
            {
                var msg =
                    log != null
                        ? log.Message
                        : string.Empty;

                throw new InvalidOperationException(
                    string.Format(
                        Messages.Composer_ErrorOnActivatingExtension,
                        extension.Id,
                        extension.Name,
                        msg));
            }
            
            var wasBusy = BeginComposition();
            var obj = InternalActivate(extension);
            
            EndComposition(wasBusy);
            
            return obj;
        }

        public object Activate(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            if (!CheckAccess())
                throw GetAccessException();
            
            _apiHook.OnExtensionOperation(ExtensionOperation.AccessObject, extension);
            
            if (InstanceStore.InternalIsActivated(extension))
            {
                if (InternalIsActivating(extension))
                    Notifier.Continue();
                
                return InstanceStore.InternalGetExtensionObject(extension);
            }
            
            LogArgs log;
            
            if (!InternalCanActivate(extension, out log))
            {
                var msg =
                    log != null
                        ? log.Message
                        : string.Empty;

                throw new InvalidOperationException(
                    string.Format(
                        Messages.Composer_ErrorOnActivatingExtension,
                        extension.Id,
                        extension.Name,
                        msg));
            }
            
            var wasBusy = BeginComposition();
            var obj = InternalActivate(extension);
            
            EndComposition(wasBusy);
            
            return obj;
        }

        public void Deactivate(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            if (!CheckAccess())
                throw GetAccessException();

            if (!InstanceStore.InternalIsActivated(extension))
            {
                if (!InternalIsDeactivating(extension))
                    return;

                Notifier.Continue();
            }
            else
            {
                LogArgs log;

                if (!InternalCanDeactivate(extension, true, out log))
                {
                    var msg =
                        log != null
                            ? log.Message
                            : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnDeactivatingExtension,
                            extension.Id,
                            extension.Name,
                            msg));
                }
                
                var wasBusy = BeginComposition();
                
                InternalDeactivate(extension);
                EndComposition(wasBusy);
            }
        }

        public void Open(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (!CheckAccess())
                throw GetAccessException();
            
            _apiHook.OnSlotOperation(SlotOperation.Open, slot);
            
            if (InstanceStore.InternalIsOpen(slot))
            {
                if (!InternalIsOpening(slot))
                    return;

                Notifier.Continue();
            }
            else
            {
                LogArgs log;

                if (!InternalCanOpen(slot, out log))
                {
                    var msg =
                        log != null
                            ? log.Message
                            : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnOpeningSlot,
                            slot.Id,
                            slot.Name,
                            msg));
                }
                
                var wasBusy = BeginComposition();
                
                if (slot.SlotType.ExtensionType.ActivationMode >= ActivationMode.OnComposition &&
                    !InstanceStore.InternalIsActivated(slot.Extension))
                {
                    InternalActivate(slot.Extension);

                    if (InstanceStore.InternalIsOpen(slot))
                        return;
                }

                InternalOpen(slot);
                EndComposition(wasBusy);
            }
        }

        public void Plug(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            if (!CheckAccess())
                throw GetAccessException();

            if (InstanceStore.InternalIsPlugged(slot, plug))
            {
                if (!InternalIsPlugging(slot, plug))
                    return;
                
                Notifier.Continue();
            }
            else
            {
                LogArgs log;

                if (!InternalCanPlug(slot, plug, out log))
                {
                    var msg =
                        log != null
                            ? log.Message
                            : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnPluggingExtension,
                            (object) plug.Extension.Id,
                            (object) plug.Extension.Name,
                            (object) slot.Extension.Id,
                            (object) slot.Extension.Name,
                            (object) slot.Name,
                            (object) msg));
                }
                
                _apiHook.OnCompositionOperation(PlugCompositionOperation.Plug, slot, plug);
                
                var wasBusy = BeginComposition();
                
                InternalPlug(slot, plug);
                EndComposition(wasBusy);
            }
        }

        public void Tag(Slot slot, Plug plug, string tag)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            if (plug == null)
                throw new ArgumentNullException("plug");
            
            if (string.IsNullOrEmpty(tag))
                throw new ArgumentException(Messages.Composer_TagMustNotBeNullOrEmpty, "tag");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            if (InstanceStore.InternalIsTagged(slot, plug, tag))
            {
                if (!InternalIsTagging(slot, plug, tag))
                    return;

                Notifier.Continue();
            }
            else
            {
                LogArgs log;

                if (!InternalCanTag(slot, plug, tag, out log))
                {
                    var msg =
                        log != null
                            ? log.Message
                            : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnTaggingExtension,
                            (object) plug.Extension.Id,
                            (object) plug.Extension.Name,
                            (object) slot.Extension.Id,
                            (object) slot.Extension.Name,
                            (object) slot.Name,
                            (object) tag,
                            (object) msg));
                }
                
                _apiHook.OnCompositionOperation(TagCompositionOperation.Tag, slot, plug, tag);
                
                var wasBusy = BeginComposition();
                
                InternalTag(slot, plug, tag);
                EndComposition(wasBusy);
            }
        }

        public void Close(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (!CheckAccess())
                throw GetAccessException();

            if (!InstanceStore.InternalIsOpen(slot))
            {
                if (!InternalIsClosing(slot))
                    return;
                
                Notifier.Continue();
            }
            else
            {
                LogArgs log;

                if (!InternalCanClose(slot, true, out log))
                {
                    var msg =
                        log != null
                            ? log.Message
                            : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnClosingSlot,
                            slot.Id,
                            slot.Name,
                            msg));
                }
                
                _apiHook.OnSlotOperation(SlotOperation.Close, slot);
                
                var wasBusy = BeginComposition();
                
                InternalClose(slot);
                EndComposition(wasBusy);
            }
        }

        public void Unplug(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            if (plug == null)
                throw new ArgumentNullException("plug");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            if (!InstanceStore.InternalIsPlugged(slot, plug))
            {
                if (!InternalIsUnplugging(slot, plug))
                    return;
                
                Notifier.Continue();
            }
            else
            {
                LogArgs log;

                if (!InternalCanUnplug(slot, plug, true, out log))
                {
                    var msg =
                        log != null
                            ? log.Message
                            : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnUnpluggingExtension,
                            (object) plug.Extension.Id,
                            (object) plug.Extension.Name,
                            (object) slot.Extension.Id,
                            (object) slot.Extension.Name,
                            (object) slot.Name,
                            (object) msg));
                }

                _apiHook.OnCompositionOperation(PlugCompositionOperation.Unplug, slot, plug);
                    
                var wasBusy = BeginComposition();
                    
                InternalUnplug(slot, plug);
                EndComposition(wasBusy);
            }
        }

        public void Untag(Slot slot, Plug plug, string tag)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            if (string.IsNullOrEmpty(tag))
                throw new ArgumentException(Messages.Composer_TagMustNotBeNullOrEmpty, "tag");

            if (!CheckAccess())
                throw GetAccessException();

            if (!InstanceStore.InternalIsTagged(slot, plug, tag))
            {
                if (!InternalIsUntagging(slot, plug, tag))
                    return;
                
                Notifier.Continue();
            }
            else
            {
                LogArgs log;

                if (!InternalCanUntag(slot, plug, tag, true, out log))
                {
                    var msg =
                        log != null
                            ? log.Message
                            : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnUntaggingExtension,
                            (object) plug.Extension.Id,
                            (object) plug.Extension.Name,
                            (object) slot.Extension.Id,
                            (object) slot.Extension.Name,
                            (object) slot.Name,
                            (object) msg));
                }
                
                var wasBusy = BeginComposition();
                
                _apiHook.OnCompositionOperation(TagCompositionOperation.Untag, slot, plug, tag);
                InternalUntag(slot, plug, tag);
                EndComposition(wasBusy);
            }
        }

        internal static void SetProperty(RuntimeElement element, Action<bool> setter,
            bool value, string error, string name)
        {
            if (!element.CheckAccess())
                throw GetAccessException();
            
            LogArgs log;
            
            if (!InternalCanSetProperty(element, out log))
                throw new InvalidOperationException(
                    string.Format(
                        error,
                        name,
                        log != null
                            ? log.Message
                            : string.Empty));
            
            setter(value);
        }

        public void SetAutoOpen(Slot slot, bool value)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (!CheckAccess())
                throw GetAccessException();

            if (InstanceStore.InternalGetAutoOpen(slot) == value)
                return;

            LogArgs log;

            if (!InternalCanSetProperty(this, out log))
                throw new InvalidOperationException(
                    string.Format(
                        Messages.Composer_ErrorOnSettingElementProperty,
                        (object) "AutoOpen",
                        (object) "Slot",
                        (object) slot.Id,
                        (object) slot.Name,
                        log != null
                            ? (object) log.Message
                            : (object) string.Empty));

            _apiHook.OnSlotOperation(
                value
                    ? SlotOperation.EnableAutoOpen
                    : SlotOperation.DisableAutoOpen, slot);

            InternalSetAutoOpen(slot, value);
        }

        public void SetAutoPlug(Slot slot, bool value)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (!CheckAccess())
                throw GetAccessException();

            if (InstanceStore.InternalGetAutoPlug(slot) == value)
                return;

            LogArgs log;

            if (!InternalCanSetProperty(this, out log))
                throw new InvalidOperationException(
                    string.Format(
                        Messages.Composer_ErrorOnSettingElementProperty,
                        (object) "AutoPlug",
                        (object) "Slot",
                        (object) slot.Id,
                        (object) slot.Name,
                        log != null
                            ? (object) log.Message
                            : (object) string.Empty));

            _apiHook.OnSlotOperation(
                value
                    ? SlotOperation.EnableAutoPlug
                    : SlotOperation.DisableAutoPlug, slot);
            
            InternalSetAutoPlug(slot, value);
        }

        public void SetAutoTag(Slot slot, string[] value)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (!CheckAccess())
                throw GetAccessException();

            if (InstanceStore.InternalGetAutoTag(slot) == value)
                return;

            LogArgs log;

            if (!InternalCanSetProperty(this, out log))
                throw new InvalidOperationException(
                    string.Format(
                        Messages.Composer_ErrorOnSettingElementProperty,
                        (object) "AutoTag",
                        (object) "Slot",
                        (object) slot.Id,
                        (object) slot.Name,
                        log != null
                            ? (object) log.Message
                            : (object) string.Empty));

            _apiHook.OnSlotOperation(
                value == null || value.Length <= 0
                    ? SlotOperation.DisableAutoTag
                    : SlotOperation.EnableAutoTag, slot);
            
            InternalSetAutoTag(slot, value);
        }

        public void SetShared(Slot slot, bool value)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (!CheckAccess())
                throw GetAccessException();

            if (InstanceStore.InternalGetShared(slot) == value)
                return;

            LogArgs log;

            if (!InternalCanSetProperty(this, out log))
                throw new InvalidOperationException(
                    string.Format(
                        Messages.Composer_ErrorOnSettingElementProperty,
                        (object) "Shared",
                        (object) "Slot",
                        (object) slot.Id,
                        (object) slot.Name,
                        log != null
                            ? (object) log.Message
                            : (object) string.Empty));

            _apiHook.OnSlotOperation(
                value
                    ? SlotOperation.EnableShared
                    : SlotOperation.DisableShared, slot);
            
            InternalSetShared(slot, value);
        }

        public void SetAutoPlug(Plug plug, bool value)
        {
            if (plug == null)
                throw new ArgumentNullException("plug");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            if (InstanceStore.InternalGetAutoPlug(plug) == value)
                return;
            
            LogArgs log;

            if (!InternalCanSetProperty(this, out log))
                throw new InvalidOperationException(
                    string.Format(
                        Messages.Composer_ErrorOnSettingElementProperty,
                        (object) "AutoPlug",
                        (object) "Plug",
                        (object) plug.Id,
                        (object) plug.Name,
                        log != null
                            ? (object) log.Message
                            : (object) string.Empty));

            _apiHook.OnPlugOperation(
                value
                    ? PlugOperation.EnableAutoPlug
                    : PlugOperation.DisableAutoPlug, plug);
            
            InternalSetAutoPlug(plug, value);
        }

        public void SetAutoTag(Plug plug, string[] value)
        {
            if (plug == null)
                throw new ArgumentNullException("plug");

            if (!CheckAccess())
                throw GetAccessException();

            if (InstanceStore.InternalGetAutoTag(plug) == value)
                return;

            LogArgs log;

            if (!InternalCanSetProperty(this, out log))
                throw new InvalidOperationException(
                    string.Format(
                        Messages.Composer_ErrorOnSettingElementProperty,
                        (object) "AutoTag",
                        (object) "Plug",
                        (object) plug.Id,
                        (object) plug.Name,
                        log != null
                            ? (object) log.Message
                            : (object) string.Empty));

            _apiHook.OnPlugOperation(
                value == null || value.Length <= 0
                    ? PlugOperation.DisableAutoTag
                    : PlugOperation.EnableAutoTag, plug);
            
            InternalSetAutoTag(plug, value);
        }

        public bool CanOpen(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (!CheckAccess())
                throw GetAccessException();

            LogArgs log;

            return InternalCanOpen(slot, out log);
        }

        public bool CanPlug(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            if (!CheckAccess())
                throw GetAccessException();
            
            LogArgs log;
            
            return InternalCanPlug(slot, plug, out log);
        }

        public bool CanTag(Slot slot, Plug plug, string tag)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            if (string.IsNullOrEmpty(tag))
                throw new ArgumentException(Messages.Composer_TagMustNotBeNullOrEmpty, "tag");

            if (!CheckAccess())
                throw GetAccessException();

            LogArgs log;
            
            return InternalCanTag(slot, plug, tag, out log);
        }

        public bool CanClose(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (!CheckAccess())
                throw GetAccessException();
            
            LogArgs log;
            
            return InternalCanClose(slot, true, out log);
        }

        public bool CanUnplug(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            if (!CheckAccess())
                throw GetAccessException();
            
            LogArgs log;
            
            return InternalCanUnplug(slot, plug, true, out log);
        }

        public bool CanUntag(Slot slot, Plug plug, string tag)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            if (string.IsNullOrEmpty(tag))
                throw new ArgumentException(Messages.Composer_TagMustNotBeNullOrEmpty, "tag");

            if (!CheckAccess())
                throw GetAccessException();

            LogArgs log;
            
            return InternalCanUntag(slot, plug, tag, true, out log);
        }

        public bool CanOpen(Slot slot, out LogArgs log)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalCanOpen(slot, out log);
        }

        public bool CanPlug(Slot slot, Plug plug, out LogArgs log)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalCanPlug(slot, plug, out log);
        }

        public bool CanTag(Slot slot, Plug plug, string tag, out LogArgs log)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            if (plug == null)
                throw new ArgumentNullException("plug");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalCanTag(slot, plug, tag, out log);
        }

        public bool CanClose(Slot slot, out LogArgs log)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalCanClose(slot, true, out log);
        }

        public bool CanUnplug(Slot slot, Plug plug, out LogArgs log)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalCanUnplug(slot, plug, true, out log);
        }

        public bool CanUntag(Slot slot, Plug plug, string tag, out LogArgs log)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            if (plug == null)
                throw new ArgumentNullException("plug");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalCanUntag(slot, plug, tag, true, out log);
        }

        public bool IsCreating(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");
            
            return InternalIsCreating(extension);
        }

        public bool IsActivating(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");
            
            return InternalIsActivating(extension);
        }

        public bool IsOpening(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            return InternalIsOpening(slot);
        }

        public bool IsPlugging(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            if (plug == null)
                throw new ArgumentNullException("plug");
            
            return InternalIsPlugging(slot, plug);
        }

        public bool IsTagging(Slot slot, Plug plug, string tag)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            if (plug == null)
                throw new ArgumentNullException("plug");
            
            return InternalIsTagging(slot, plug, tag);
        }

        public bool IsDestroying(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");
            
            return InternalIsDestroying(extension);
        }

        public bool IsDeactivating(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");
            
            return InternalIsDeactivating(extension);
        }

        public bool IsClosing(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            return InternalIsClosing(slot);
        }

        public bool IsUnplugging(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            if (plug == null)
                throw new ArgumentNullException("plug");
            
            return InternalIsUnplugging(slot, plug);
        }

        public bool IsUntagging(Slot slot, Plug plug, string tag)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");
            
            if (plug == null)
                throw new ArgumentNullException("plug");
            
            if (tag == null)
                throw new ArgumentNullException("tag");
            
            return InternalIsUntagging(slot, plug, tag);
        }

        public void InvokeOnCompositionDone(Action handler)
        {
            if (handler == null)
                throw new ArgumentNullException("handler");

            if (!CheckAccess())
                throw GetAccessException();

            Notifier.InvokeOnCompositionDone(handler);
        }

        public void InvokeOnCompositionDone<T>(Action<T> handler, T args)
        {
            if (handler == null)
                throw new ArgumentNullException("handler");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            Notifier.InvokeOnCompositionDone(handler, args);
        }

        public void InvokeOnCompositionDone(Delegate @delegate, params object[] args)
        {
            if (@delegate == null)
                throw new ArgumentNullException("delegate");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            Notifier.InvokeOnCompositionDone(@delegate, args);
        }

        protected internal virtual Extension InternalCreate(ExtensionType extensionType)
        {
            var extension =
                _instanceBuilder.CreateExtension(extensionType, Qualifier);

            AddCreating(extension);
            InstanceStoreModifier.InternalAddExtension(extension);
            Notifier.OnCreated(new ExtensionEventArgs(extension));
            RemoveCreating(extension);
            
            if (!InstanceStore.IsActivated(extension) &&
                extension.ExtensionType.ActivationMode == ActivationMode.OnCreate)
                InternalActivate(extension);
            
            return extension;
        }

        internal Extension InternalCreateShared(ExtensionType extensionType)
        {
            var extension = InternalCreate(extensionType);
            
            InstanceStoreModifier.InternalSetSharedExtension(extension);
            
            return extension;
        }

        protected virtual object InternalActivate(Extension extension)
        {
            Debug.Assert(!InstanceStore.InternalIsActivated(extension));
            AddActivating(extension);

            var args = new ExtensionEventArgs(extension);

            Notifier.OnActivating(args);
            object instance;

            try
            {
                instance = CreateInstance(extension);
            }
            catch
            {
                RemoveActivating(extension);
                throw;
            }

            Notifier.AddReflectionHandlers(extension, instance);
            InstanceStoreModifier.InternalAddActivated(extension, instance);
            
            Notifier.OnActivated(args);
            Engine.InternalAutoOpenSlots(extension);
            RemoveActivating(extension);
            
            Debug.Assert(InstanceStore.InternalIsActivated(extension));
            
            return extension.extensionObject;
        }

        protected virtual object CreateInstance(Extension extension)
        {
            return Activator.CreateInstance(extension);
        }

        protected internal virtual void InternalOpen(Slot slot)
        {
            Debug.Assert(
                slot.SlotType.ExtensionType.ActivationMode < ActivationMode.OnComposition ||
                InstanceStore.InternalIsActivated(slot.Extension));
            
            Debug.Assert(!InstanceStore.InternalIsOpen(slot));

            var args = new SlotEventArgs(slot);

            AddOpening(slot);

            Notifier.OnOpening(args);
            InstanceStoreModifier.InternalAddOpened(slot);

            Notifier.OnOpened(args);
            Engine.InternalAutoTagAndPlug(slot);

            RemoveOpening(slot);

            Debug.Assert(InstanceStore.InternalIsOpen(slot));
        }

        protected internal virtual void InternalPlug(Slot slot, Plug plug)
        {
            _substitutionHook.SubstituteForPlugging(ref slot, ref plug);

            Debug.Assert(InstanceStore.InternalIsOpen(slot));
            Debug.Assert(!InstanceStore.InternalIsPlugged(slot, plug));

            AddPlugging(slot, plug);
            
            var args = new CompositionEventArgs(slot, plug);
            
            Notifier.OnPlugging(args);
            
            if (!string.IsNullOrEmpty(plug.PlugType.OnPlugged) &&
                !InstanceStore.InternalIsActivated(plug.Extension))
                InternalActivateForHandler(plug);
            
            InstanceStoreModifier.InternalAddPlugged(slot, plug);
            Notifier.OnPlugged(args);
            RemovePlugging(slot, plug);
            
            Debug.Assert(InstanceStore.InternalIsPlugged(slot, plug));
        }

        protected internal virtual void InternalTag(Slot slot, Plug plug, string name)
        {
            Debug.Assert(!InstanceStore.InternalIsTagged(slot, plug, name));
            _substitutionHook.SubstituteForTagging(ref slot, ref plug, ref name);

            var tag = _instanceBuilder.CreateTag(name, slot, plug);
            AddTagging(tag);

            var args = new TagEventArgs(tag);
            Notifier.OnTagging(args);

            if (!string.IsNullOrEmpty(plug.PlugType.OnTagged) &&
                !InstanceStore.InternalIsActivated(plug.Extension))
                InternalActivateForHandler(plug);
            
            InstanceStoreModifier.InternalAddTag(tag);
            Notifier.OnTagged(args);
            RemoveTagging(tag);
            
            Debug.Assert(InstanceStore.InternalIsTagged(slot, plug, name));
        }

        protected internal virtual void InternalDestroy(Extension extension)
        {
            Debug.Assert(!InstanceStore.InternalIsDestroyed(extension));
            AddDestroying(extension);

            foreach (var plug in extension.Plugs)
                InternalUnplugAll(plug);

            foreach (var plug in extension.Plugs)
                InternalUntagAll(plug);

            if (!InternalIsDeactivating(extension) &&
                InstanceStore.InternalIsActivated(extension))
                InternalDeactivate(extension);

            InternalCloseAllSlots(extension, false);
            InternalUnbindBehaviors(extension);

            InstanceStoreModifier.InternalRemoveExtension(extension);
            Notifier.OnDestroyed(new ExtensionEventArgs(extension));
            RemoveDestroying(extension);
            
            Debug.Assert(InstanceStore.InternalIsDestroyed(extension));
        }

        internal void InternalDeactivate(Extension extension)
        {
            Debug.Assert(InstanceStore.IsActivated(extension));
            AddDeactivating(extension, extension.Object);

            var args = new ExtensionEventArgs(extension);
            Notifier.OnDeactivating(args);

            InternalCloseAllSlots(extension, true);

            if (extension.Object is IDisposable)
            {
                try
                {
                    Invoker.Invoke(extension, typeof(IDisposable).GetMethod("Dispose", Type.EmptyTypes), new object[0]);
                }
                catch (TargetInvocationException ex)
                {
                    Logger.LogUnhandledException(new ExceptionEventArgs(ex.InnerException));
                }
            }

            InstanceStoreModifier.InternalRemoveActivated(extension);
            Notifier.RemoveReflectionHandlers(extension);

            Notifier.OnDeactivated(args);
            RemoveDeactivating(extension);
            
            Debug.Assert(!InstanceStore.IsActivated(extension));
        }

        private void InternalClose(Slot slot)
        {
            Debug.Assert(InstanceStore.InternalIsOpen(slot));
            AddClosing(slot);

            var args = new SlotEventArgs(slot);
            Notifier.OnClosing(args);

            InternalUnplugAll(slot);
            InternalUntagAll(slot);

            InstanceStoreModifier.InternalRemoveOpened(slot);

            Notifier.OnClosed(args);
            RemoveClosing(slot);
            
            Debug.Assert(!InstanceStore.InternalIsOpen(slot));
        }

        protected virtual void InternalUnplug(Slot slot, Plug plug)
        {
            _substitutionHook.SubstituteForUnplugging(ref slot, ref plug);

            Debug.Assert(InstanceStore.InternalIsPlugged(slot, plug));
            AddUnplugging(slot, plug);

            var args = new CompositionEventArgs(slot, plug);
            Notifier.OnUnplugging(args);

            if (!string.IsNullOrEmpty(plug.PlugType.OnUnplugged) &&
                !InstanceStore.InternalIsActivated(plug.Extension))
                InternalActivateForHandler(plug);
            
            InstanceStoreModifier.InternalRemovePlugged(slot, plug);

            Notifier.OnUnplugged(args);
            RemoveUnplugging(slot, plug);
            
            Debug.Assert(!InstanceStore.InternalIsPlugged(slot, plug));
            
            GC.InternalScheduleInspectionOf(new[] { plug.Extension });
        }

        protected virtual void InternalUntag(Slot slot, Plug plug, string name)
        {
            _substitutionHook.SubstituteForUntagging(ref slot, ref plug, ref name);

            Debug.Assert(InstanceStore.InternalIsTagged(slot, plug, name));

            var tag = InstanceStore.InternalGetTag(slot, plug, name);
            AddUntagging(tag);

            var args = new TagEventArgs(tag);
            Notifier.OnUntagging(args);

            if (!string.IsNullOrEmpty(plug.PlugType.OnUntagged) &&
                !InstanceStore.InternalIsActivated(plug.Extension))
                InternalActivateForHandler(plug);

            InstanceStoreModifier.RemoveTag(tag);

            Notifier.OnUntagged(args);
            RemoveUntagging(tag);
            
            Debug.Assert(!InstanceStore.InternalIsTagged(slot, plug, name));
            
            GC.InternalScheduleInspectionOf(new[] { plug.Extension });
        }

        private void InternalActivateForHandler(Plug plug)
        {
            var extension = plug.Extension;
            LogArgs log;

            if (Notifier.CanActivate(new ExtensionEventArgs(extension), out log))
                InternalActivate(extension);
            else
            {
                var msg =
                    log != null
                        ? log.Message
                        : Messages.Composer_Error_CanHandlerReturnedFalse;

                Logger.Log(
                    new LogArgs(
                        LogType.Error,
                        this,
                        string.Format(
                            Messages.Composer_ActivatingExtensionCanceled,
                            extension.Id,
                            extension.Name,
                            msg),
                        new object[0]));
            }
        }

        protected internal virtual void InternalSetAllowComposition(bool value)
        {
            allowComposition = value;
        }

        protected internal virtual void InternalSetAutoOpen(Slot slot, bool value)
        {
            InstanceStoreModifier.InternalSetAutoOpen(slot, value);
        }

        protected internal virtual void InternalSetAutoPlug(Slot slot, bool value)
        {
            InstanceStoreModifier.InternalSetAutoPlug(slot, value);
        }

        protected internal virtual void InternalSetAutoTag(Slot slot, string[] value)
        {
            InstanceStoreModifier.InternalSetAutoTag(slot, value);
        }

        protected internal virtual void InternalSetShared(Slot slot, bool value)
        {
            InstanceStoreModifier.InternalSetShared(slot, value);
        }

        protected internal virtual void InternalSetAutoPlug(Plug plug, bool value)
        {
            InstanceStoreModifier.InternalSetAutoPlug(plug, value);
        }

        protected internal virtual void InternalSetAutoTag(Plug plug, string[] value)
        {
            InstanceStoreModifier.InternalSetAutoTag(plug, value);
        }

        internal bool BeginComposition()
        {
            if (IsBusy)
                return true;
            
            IsBusy = true;
            
            return false;
        }

        internal void EndComposition(bool wasBusy)
        {
            if (wasBusy)
                return;
            
            IsBusy = false;
            
            Notifier.InvokeOnDoneHandlers();
        }

        private void InternalDestroyAllExtensions(Plugin plugin)
        {
            foreach (var extension in
                from extensionType in plugin.ExtensionTypes
                from extension in InstanceStore.InternalGetExtensions(extensionType)
                where !InstanceStore.InternalIsDestroyed(extension) &&
                      !InternalIsDestroying(extension)
                select extension)
                InternalDestroy(extension);
        }

        private void InternalUnbindBehaviors(Extension extension)
        {
            foreach (var slot in extension.Slots)
                slot.Behaviors.Clear();
        }

        private void InternalCloseAllSlots(Extension extension, bool autoActivatedOnly)
        {
            foreach (var slot in extension.Slots.Where(
                slot => InstanceStore.InternalIsOpen(slot) &&
                        !InternalIsClosing(slot) &&
                        (!autoActivatedOnly ||
                         slot.SlotType.ExtensionType.ActivationMode >= ActivationMode.OnComposition)))
                InternalClose(slot);
        }

        private void InternalUntagAll(Plug plug)
        {
            foreach (var tag in InstanceStore.InternalGetTags(plug).Where(
                tag => InstanceStore.InternalIsTagged(tag.Slot, tag.Plug, tag.Name) &&
                       !InternalIsUntagging(tag.Slot, tag.Plug, tag.Name)))
                InternalUntag(tag.Slot, plug, tag.Name);
        }

        private void InternalUntagAll(Slot slot)
        {
            foreach (var tag in InstanceStore.InternalGetTags(slot).Where(
                tag => InstanceStore.InternalIsTagged(tag.Slot, tag.Plug, tag.Name) &&
                       !InternalIsUntagging(tag.Slot, tag.Plug, tag.Name)))
                InternalUntag(slot, tag.Plug, tag.Name);
        }

        private void InternalUnplugAll(Plug plug)
        {
            foreach (var slot in InstanceStore.InternalGetSlotsWherePlugged(plug).Where(
                slot => InstanceStore.InternalIsPlugged(slot, plug) &&
                        !InternalIsUnplugging(slot, plug)))
                InternalUnplug(slot, plug);
        }

        private void InternalUnplugAll(Slot slot)
        {
            foreach (var plug in InstanceStore.InternalGetPluggedPlugs(slot).Where(
                plug => InstanceStore.InternalIsPlugged(slot, plug) &&
                        !InternalIsUnplugging(slot, plug)))
                InternalUnplug(slot, plug);
        }

        private bool InternalCanCreate(ExtensionType extensionType, out LogArgs log)
        {
            if (!_compositionPermissionHook.CanCreate(extensionType, out log))
                return false;

            if (!AllowComposition)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (Notifier.IsExecutingCanHandler)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                    new object[0]);
                
                return false;
            }
            
            if (!Qualifier.InternalWeakQualify(extensionType))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    string.Format(
                        Messages.Composer_Error_ElementIsNotQualified,
                        (object) "Extension type",
                        (object) extensionType.Id,
                        (object) extensionType.Name,
                        (object) extensionType.QualificationState),
                    new object[0]);
                
                return false;
            }

            log = null;

            return true;
        }

        private bool InternalCanActivate(Extension extension, out LogArgs log)
        {
            if (InternalIsActivating(extension))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_RecursiveCallIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (InstanceStore.InternalIsActivated(extension))
            {
                log = new LogArgs(
                    LogType.Warning,
                    this,
                    Messages.Composer_Error_ExtensionIsAlreadyActivated,
                    new object[0]);
                
                return false;
            }
            
            if (!AllowComposition)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (Notifier.IsExecutingCanHandler)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                    new object[0]);
                
                return false;
            }
            if (InstanceStore.InternalIsDestroyed(extension))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_ExtensionIsDestroyed,
                    new object[0]);
                
                return false;
            }
            
            if (InternalIsDestroying(extension))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_ExtensionIsDestroying,
                    new object[0]);
                
                return false;
            }
            if (InternalIsDeactivating(extension))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_ExtensionIsDeactivating,
                    new object[0]);
                
                return false;
            }
            
            if (!Notifier.CanActivate(new ExtensionEventArgs(extension), out log))
                return false;
            
            log = null;
            
            return true;
        }

        private bool InternalCanOpen(Slot slot, out LogArgs log)
        {
            if (InternalIsOpening(slot))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_RecursiveCallIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (InstanceStore.InternalIsOpen(slot))
            {
                log = new LogArgs(
                    LogType.Warning,
                    this,
                    Messages.Composer_Error_SlotIsAlreadyOpen,
                    new object[0]);
                
                return false;
            }

            if (InstanceStore.InternalIsActivated(slot.Extension))
            {
                if (!AllowComposition)
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_CompositionIsNotAllowed,
                        new object[0]);
                    
                    return false;
                }
                
                if (Notifier.IsExecutingCanHandler)
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                        new object[0]);
                    
                    return false;
                }
                
                if (InternalIsDeactivating(slot.Extension))
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_HostExtensionIsDeactivating,
                        new object[0]);
                    
                    return false;
                }
                
                if (InternalIsClosing(slot))
                {
                    log = new LogArgs(
                        LogType.Error, this,
                        Messages.Composer_Error_SlotIsClosing,
                        new object[0]);
                    
                    return false;
                }
            }
            else if (!InternalCanActivate(slot.Extension, out log))
                return false;

            var slotType = slot.SlotType;

            if (!Qualifier.InternalWeakQualify(slotType))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    string.Format(
                        Messages.Composer_Error_ElementIsNotQualified,
                        (object) "Slot type",
                        (object) slotType.Id,
                        (object) slotType.Name,
                        (object) slotType.QualificationState),
                    new object[0]);
                
                return false;
            }
            
            if (!Notifier.CanOpen(new SlotEventArgs(slot), out log))
                return false;

            log = null;

            return true;
        }

        private bool InternalCanPlug(Slot slot, Plug plug, out LogArgs log)
        {
            if (InternalIsPlugging(slot, plug))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_RecursiveCallIsNotAllowed,
                    new object[0]);

                return false;
            }
            
            if (InstanceStore.InternalIsPlugged(slot, plug))
            {
                log = new LogArgs(
                    LogType.Warning,
                    this,
                    Messages.Composer_Error_PlugIsAlreadyPlugged,
                    new object[0]);
                
                return false;
            }
            
            if (InstanceStore.InternalIsOpen(slot))
            {
                if (!AllowComposition)
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_CompositionIsNotAllowed,
                        new object[0]);
                    
                    return false;
                }
                if (Notifier.IsExecutingCanHandler)
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                        new object[0]);
                    
                    return false;
                }
                
                if (InternalIsClosing(slot))
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_SlotIsClosing,
                        new object[0]);
                    
                    return false;
                }
                
                if (InternalIsUnplugging(slot, plug))
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_SlotIsUnpluggingThePlug,
                        new object[0]);
                    
                    return false;
                }
            }
            else if (!InternalCanOpen(slot, out log))
                return false;

            if (slot.Name != plug.Name)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_PlugIsNotCompatibleToSlot,
                    new object[0]);
                
                return false;
            }
            
            var plugType = plug.PlugType;
            
            if (!Qualifier.InternalWeakQualify(plugType))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    string.Format(
                        Messages.Composer_Error_ElementIsNotQualified,
                        (object) "Plug type",
                        (object) plugType.Id,
                        (object) plugType.Name,
                        (object) plugType.QualificationState),
                    new object[0]);
                
                return false;
            }
            
            if (!InternalCanComposeContributor(plug.Extension, out log) ||
                !Notifier.CanPlug(new CompositionEventArgs(slot, plug), out log))
                return false;

            log = null;

            return true;
        }

        private bool InternalCanTag(Slot slot, Plug plug, string name, out LogArgs log)
        {
            if (InternalIsTagging(slot, plug, name))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_RecursiveCallIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            if (InstanceStore.InternalIsTagged(slot, plug, name))
            {
                log = new LogArgs(
                    LogType.Warning,
                    this,
                    Messages.Composer_Error_PlugIsAlreadyTagged,
                    new object[0]);
                
                return false;
            }
            
            if (InstanceStore.InternalIsOpen(slot))
            {
                if (!AllowComposition)
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_CompositionIsNotAllowed,
                        new object[0]);
                    
                    return false;
                }
                
                if (Notifier.IsExecutingCanHandler)
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                        new object[0]);
                    
                    return false;
                }
                
                if (InternalIsUntagging(slot, plug, name))
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_SlotIsUntaggingThePlug,
                        new object[0]);
                    
                    return false;
                }
            }
            else if (!InternalCanOpen(slot, out log))
                return false;

            var tagDefinitions = slot.SlotType.SlotDefinition.TagDefinitions;
            
            if (!tagDefinitions.Contains(name))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_TagDefinitionNotFound,
                    new object[0]);
                
                return false;
            }
            
            var tagDefinition = tagDefinitions[name];
            
            if (!Qualifier.InternalWeakQualify(tagDefinition))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    string.Format(
                        Messages.Composer_Error_ElementIsNotQualified,
                        (object) "Tag definition",
                        (object) tagDefinition.Id,
                        (object) tagDefinition.Name,
                        (object) tagDefinition.QualificationState),
                    new object[0]);
                    
                return false;
            }
            
            var plugType = plug.PlugType;
            
            if (!Qualifier.InternalWeakQualify(plugType))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    string.Format(
                        Messages.Composer_Error_ElementIsNotQualified,
                        (object) "Plug type",
                        (object) plugType.Id,
                        (object) plugType.Name,
                        (object) plugType.QualificationState),
                    new object[0]);
                        
                return false;
            }
            
            if (!InternalCanComposeContributor(plug.Extension, out log) ||
                !Notifier.CanTag(new TagEventArgs(slot, plug, name), out log))
                return false;

            log = null;

            return true;
        }

        private bool InternalCanComposeContributor(Extension contributor, out LogArgs log)
        {
            if (InternalIsDeactivating(contributor))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_ContributorExtensionIsDeactivating,
                    new object[0]);
                
                return false;
            }
            
            if (InternalIsDestroying(contributor))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_ContributorExtensionIsDestroying,
                    new object[0]);
                
                return false;
            }
            log = null;

            return true;
        }

        private bool InternalCanDestroy(Extension extension, out LogArgs log)
        {
            if (InternalIsDestroying(extension))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_RecursiveCallIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (InstanceStore.InternalIsDestroyed(extension))
            {
                log = new LogArgs(
                    LogType.Warning,
                    this,
                    Messages.Composer_Error_ExtensionIsAlreadyDestroyed,
                    new object[0]);
                
                return false;
            }
            
            if (!AllowComposition)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (Notifier.IsExecutingCanHandler)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                    new object[0]);
                
                return false;
            }
            if (InternalIsCreating(extension))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_ExtensionIsCreating,
                    new object[0]);
                
                return false;
            }
            
            if (InternalIsActivating(extension))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_ExtensionIsActivating,
                    new object[0]);
                
                return false;
            }

            foreach (var plug in extension.Plugs)
                if (InternalIsComposing(plug, out log))
                    return false;

            foreach (var slot in extension.Slots)
                if (InternalIsComposing(slot, out log))
                    return false;

            log = null;

            return true;
        }

        private bool InternalCanDeactivate(Extension extension, bool notifyCanHandler, out LogArgs log)
        {
            if (InternalIsDeactivating(extension))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_RecursiveCallIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (!InstanceStore.InternalIsActivated(extension))
            {
                log = new LogArgs(
                    LogType.Warning,
                    this,
                    Messages.Composer_Error_ExtensionIsNotActivated,
                    new object[0]);
                
                return false;
            }
            
            if (!AllowComposition)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (Notifier.IsExecutingCanHandler)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                    new object[0]);
                
                return false;
            }
            
            if (InternalIsActivating(extension))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_ExtensionIsActivating,
                    new object[0]);
                
                return false;
            }
            
            foreach (var plug in extension.Plugs)
            {
                using (var slotEnum = InstanceStore.InternalGetSlotsWherePlugged(plug).GetEnumerator())
                {
                    if (slotEnum.MoveNext())
                    {
                        var ext = slotEnum.Current.Extension;

                        log = new LogArgs(
                            LogType.Error,
                            this,
                            Messages.Composer_Error_PlugXIsPluggedIntoExtensionY,
                            new object[]
                                {
                                    plug.Id,
                                    plug.Name,
                                    ext.Id,
                                    ext.Name
                                });

                        return false;
                    }
                }

                if (InternalIsComposing(plug, out log))
                    return false;
            }

            foreach (var slot in extension.Slots)
                if (InternalIsComposing(slot, out log))
                    return false;

            if (notifyCanHandler && !Notifier.CanDeactivate(new ExtensionEventArgs(extension), out log))
                return false;
            
            log = null;
            
            return true;
        }

        private bool InternalCanClose(Slot slot, bool notifyCanHandler, out LogArgs log)
        {
            if (InternalIsClosing(slot))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_RecursiveCallIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (!InstanceStore.InternalIsOpen(slot))
            {
                log = new LogArgs(
                    LogType.Warning,
                    this,
                    Messages.Composer_Error_SlotIsNotOpen,
                    new object[0]);
                
                return false;
            }
            
            if (!AllowComposition)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (Notifier.IsExecutingCanHandler)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                    new object[0]);
                
                return false;
            }
            
            if (InternalIsComposing(slot, out log) ||
                notifyCanHandler &&
                !Notifier.CanClose(new SlotEventArgs(slot), out log))
                return false;

            log = null;

            return true;
        }

        private bool InternalCanUnplug(Slot slot, Plug plug, bool notifyCanHandler, out LogArgs log)
        {
            if (InternalIsUnplugging(slot, plug))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_RecursiveCallIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (!InstanceStore.InternalIsPlugged(slot, plug))
            {
                log = new LogArgs(
                    LogType.Warning,
                    this,
                    string.Format(
                        Messages.Composer_Error_PlugIsNotPlugged,
                        (object) plug.Id,
                        (object) plug.Name,
                        (object) slot.Id,
                        (object) slot.Name),
                    new object[0]);
                
                return false;
            }
            
            if (!AllowComposition)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (Notifier.IsExecutingCanHandler)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                    new object[0]);
                
                return false;
            }
            
            if (InternalIsPlugging(slot, plug))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_SlotIsPluggingIntoPlug,
                    new object[0]);
                
                return false;
            }
            
            if (notifyCanHandler && !Notifier.CanUnplug(new CompositionEventArgs(slot, plug), out log))
                return false;

            log = null;

            return true;
        }

        private bool InternalCanUntag(Slot slot, Plug plug, string name, bool notifyCanHandler, out LogArgs log)
        {
            if (InternalIsUntagging(slot, plug, name))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_RecursiveCallIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (!InstanceStore.InternalIsTagged(slot, plug, name))
            {
                log = new LogArgs(
                    LogType.Warning,
                    this,
                    Messages.Composer_Error_PlugIsNotTagged,
                    new object[0]);
                
                return false;
            }
            
            if (!AllowComposition)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (Notifier.IsExecutingCanHandler)
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                    new object[0]);
                
                return false;
            }
            
            if (InternalIsTagging(slot, plug, name))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_PlugIsTaggingTheTagToTheSlot,
                    new object[0]);
                
                return false;
            }
            
            if (notifyCanHandler && !Notifier.CanUntag(
                new TagEventArgs(InstanceStore.InternalGetTag(slot, plug, name)), out log))
                return false;

            log = null;

            return true;
        }

        private bool InternalIsComposing(Plug plug, out LogArgs log)
        {
            using (var tagEnum = GetTaggingTags(plug).GetEnumerator())
            {
                if (tagEnum.MoveNext())
                {
                    var currentTag = tagEnum.Current;
                    var extension = currentTag.Slot.Extension;

                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_PlugXIsTaggingToExtensionY,
                        new object[]
                            {
                                plug.Id,
                                plug.Name,
                                extension.Id,
                                extension.Name,
                                currentTag.Name
                            });

                    return true;
                }
            }

            using (var slotEnum = GetSlotsWherePlugging(plug).GetEnumerator())
            {
                if (slotEnum.MoveNext())
                {
                    var extension = slotEnum.Current.Extension;

                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.Composer_Error_PlugXIsPluggingToExtensionY,
                        new object[]
                            {
                                plug.Id,
                                plug.Name,
                                extension.Id,
                                extension.Name
                            });

                    return true;
                }
            }

            log = null;
            
            return false;
        }

        private bool InternalIsComposing(Slot slot, out LogArgs log)
        {
            if (InternalIsOpening(slot))
            {
                log = new LogArgs(
                    LogType.Error,
                    this,
                    Messages.Composer_Error_SlotXIsOpening,
                    new object[]
                        {
                            slot.Id,
                            slot.Name
                        });

                return true;
            }
            
            using (var tagEnum = GetTaggingTags(slot).GetEnumerator())
            {
                if (tagEnum.MoveNext())
                {
                    var currentTag = tagEnum.Current;
                    var extension = currentTag.Plug.Extension;

                    log = new LogArgs(
                        LogType.Error,
                        this, string.Format(
                            Messages.Composer_Error_ExtensionXIsTaggingIntoSlotY,
                            (object) extension.Id,
                            (object) extension.Name,
                            (object) slot.Id,
                            (object) slot.Name,
                            (object) currentTag.Name),
                        new object[0]);
                    
                    return true;
                }
            }
            
            using (var plugEnum = GetPluggingPlugs(slot).GetEnumerator())
            {
                if (plugEnum.MoveNext())
                {
                    var extension = plugEnum.Current.Extension;

                    log = new LogArgs(
                        LogType.Error,
                        this,
                        string.Format(
                            Messages.Composer_Error_ExtensionXIsPluggingIntoSlotY,
                            (object) extension.Id,
                            (object) extension.Name,
                            (object) slot.Id,
                            (object) slot.Name),
                        new object[0]);
                    
                    return true;
                }
            }

            log = null;
            return false;
        }

        private static bool InternalCanSetProperty(RuntimeElement element, out LogArgs log)
        {
            if (!element.Runtime.Composer.AllowComposition)
            {
                log = new LogArgs(
                    LogType.Error,
                    element,
                    Messages.Composer_Error_CompositionIsNotAllowed,
                    new object[0]);
                
                return false;
            }
            
            if (element.Runtime.Notifier.IsExecutingCanHandler)
            {
                log = new LogArgs(
                    LogType.Error,
                    element,
                    Messages.Composer_Error_CompositionIsNotAllowedInCanHandler,
                    new object[0]);
                
                return false;
            }

            log = null;

            return true;
        }

        internal bool InternalIsCreating(Extension extension)
        {
            return IsCreatingSet.Contains(extension);
        }

        internal bool InternalIsActivating(Extension extension)
        {
            return IsActivatingSet.Contains(extension);
        }

        internal bool InternalIsOpening(Slot slot)
        {
            return IsOpeningSet.Contains(slot);
        }

        internal bool InternalIsPlugging(Slot slot, Plug plug)
        {
            return IsPluggingDictionary.ContainsKey(slot) &&
                IsPluggingDictionary[slot].Contains(plug);
        }

        private IEnumerable<Plug> GetPluggingPlugs(Slot slot)
        {
            return
                IsPluggingDictionary.ContainsKey(slot)
                    ? IsPluggingDictionary[slot]
                    : (IEnumerable<Plug>) new Plug[0];
        }

        private IEnumerable<Slot> GetSlotsWherePlugging(Plug plug)
        {
            return (from pair in IsPluggingDictionary
                    where pair.Value.Contains(plug)
                    select pair.Key).ToList();
        }

        internal bool InternalIsTagging(Slot slot, Plug plug, string name)
        {
            return
                IsTaggingDictionary.ContainsKey(slot) &&
                IsTaggingDictionary[slot].Any(
                    tag => tag.Plug == plug &&
                           tag.Name == name);
        }

        private IEnumerable<Tag> GetTaggingTags(Plug plug)
        {
            return from tags in IsTaggingDictionary.Values
                   from tag in tags
                   where tag.Plug == plug
                   select tag;
        }

        private IEnumerable<Tag> GetTaggingTags(Slot slot)
        {
            if (!IsTaggingDictionary.ContainsKey(slot))
                yield break;

            foreach (var tag in IsTaggingDictionary[slot])
                yield return tag;
        }

        internal bool InternalIsDestroying(Extension extension)
        {
            return IsDestroyingSet.Contains(extension);
        }

        internal bool InternalIsDeactivating(Extension extension)
        {
            return IsDeactivatingDictionary.ContainsKey(extension);
        }

        internal bool InternalIsClosing(Slot slot)
        {
            return IsClosingSet.Contains(slot);
        }

        internal bool InternalIsUnplugging(Slot slot, Plug plug)
        {
            return
                IsUnpluggingDictionary.ContainsKey(slot) &&
                IsUnpluggingDictionary[slot].Contains(plug);
        }

        internal bool InternalIsUntagging(Slot slot, Plug plug, string name)
        {
            return
                IsUntaggingDictionary.ContainsKey(slot) &&
                IsUntaggingDictionary[slot].Any(
                    tag => tag.Plug == plug &&
                           tag.Name == name);
        }

        internal void AddCreating(Extension extension)
        {
            IsCreatingSet.Add(extension);
        }

        internal void RemoveCreating(Extension extension)
        {
            IsCreatingSet.Remove(extension);
        }

        internal void AddActivating(Extension extension)
        {
            IsActivatingSet.Add(extension);
        }

        internal void RemoveActivating(Extension extension)
        {
            IsActivatingSet.Remove(extension);
        }

        internal void AddOpening(Slot slot)
        {
            IsOpeningSet.Add(slot);
        }

        internal void RemoveOpening(Slot slot)
        {
            IsOpeningSet.Remove(slot);
        }

        internal void AddPlugging(Slot slot, Plug plug)
        {
            (IsPluggingDictionary.ContainsKey(slot)
                 ? IsPluggingDictionary[slot]
                 : (IsPluggingDictionary[slot] =
                    new List<Plug>(1))).Add(plug);
        }

        internal void RemovePlugging(Slot slot, Plug plug)
        {
            var plugs = IsPluggingDictionary[slot];
            
            if (plugs.Count == 1)
                IsPluggingDictionary.Remove(slot);
            else
                plugs.Remove(plug);
        }

        internal void AddTagging(Tag tag)
        {
            var slot = tag.Slot;

            (IsTaggingDictionary.ContainsKey(slot)
                 ? IsTaggingDictionary[slot]
                 : (IsTaggingDictionary[slot] =
                    new List<Tag>(1))).Add(tag);
        }

        internal void RemoveTagging(Tag tag)
        {
            var slot = tag.Slot;
            var tags = IsTaggingDictionary[slot];
            
            if (tags.Count == 1)
                IsTaggingDictionary.Remove(slot);
            else
                tags.Remove(tag);
        }

        internal void AddDestroying(Extension extension)
        {
            IsDestroyingSet.Add(extension);
        }

        internal void RemoveDestroying(Extension extension)
        {
            IsDestroyingSet.Remove(extension);
        }

        internal void AddDeactivating(Extension extension, object extensionObject)
        {
            IsDeactivatingDictionary.Add(extension, extensionObject);
        }

        internal void RemoveDeactivating(Extension extension)
        {
            IsDeactivatingDictionary.Remove(extension);
        }

        internal void AddClosing(Slot slot)
        {
            IsClosingSet.Add(slot);
        }

        internal void RemoveClosing(Slot slot)
        {
            IsClosingSet.Remove(slot);
        }

        internal void AddUnplugging(Slot slot, Plug plug)
        {
            (IsUnpluggingDictionary.ContainsKey(slot)
                 ? IsUnpluggingDictionary[slot]
                 : (IsUnpluggingDictionary[slot] =
                    new List<Plug>(1))).Add(plug);
            
            _notificationHook.OnUnplugStarted(slot, plug);
        }

        internal void RemoveUnplugging(Slot slot, Plug plug)
        {
            var plugs = IsUnpluggingDictionary[slot];

            if (plugs.Count == 1)
                IsUnpluggingDictionary.Remove(slot);
            else
                plugs.Remove(plug);

            _notificationHook.OnUnplugFinished(slot, plug);
        }

        internal void AddUntagging(Tag tag)
        {
            var slot = tag.Slot;

            (IsUntaggingDictionary.ContainsKey(slot)
                 ? IsUntaggingDictionary[slot]
                 : (IsUntaggingDictionary[slot] =
                    new List<Tag>(1))).Add(tag);
            
            _notificationHook.OnUntagStarted(tag.Slot, tag.Plug, tag.Name);
        }

        internal void RemoveUntagging(Tag tag)
        {
            var slot = tag.Slot;
            var tags = IsUntaggingDictionary[slot];
            
            if (tags.Count == 1)
                IsUntaggingDictionary.Remove(slot);
            else
                tags.Remove(tag);
           
            _notificationHook.OnUntagFinished(tag.Slot, tag.Plug, tag.Name);
        }

        internal bool HasPluggedPlugs(Extension extension)
        {
            return
                extension.Plugs.Any(
                    plug => InstanceStore.InternalGetSlotsWherePlugged(plug).Count > 0);
        }

        internal bool HasTaggedPlugs(Extension extension)
        {
            return extension.Plugs.Any(
                plug => InstanceStore.InternalGetTags(plug).Count > 0);
        }
    }
}
